package com.xk.ucs.modbus.handler;

import cn.hutool.core.lang.Pair;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
import com.serotonin.modbus4j.msg.WriteRegisterResponse;
import com.xk.ucs.common.ErrorEnum;
import com.xk.ucs.config.GlobalSetting;
import com.xk.ucs.modbus.CommonEum;
import com.xk.ucs.modbus.config.ModBusProperties;
import com.xk.ucs.modbus.enums.ModbusOrderEnum;
import com.xk.ucs.modbus.pojo.LightControlMode;
import com.xk.ucs.service.AdminRemoteService;
import com.xk.ucs.service.HeartBeatService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static com.xk.ucs.modbus.CommonEum.*;
import static com.xk.ucs.modbus.enums.ModbusOrderEnum.*;


/**
 * Date:2021/1/1 Author：01398017 Description: 实现类
 **/
@Component
@Slf4j
public class ModbusHandlerImpl implements ModbusHandler {

	@Value("${modbus.init}")
	private boolean inited;
	private static long REFUND_TIME_OUT=1000*60*2;
	public static ThreadLocal<Boolean> isLog = ThreadLocal.withInitial(() -> true);

	@Autowired(required = false)
	private ModbusMaster modbusMaster;

	private int slaveId;

	@Autowired(required = false)
	private ModBusProperties modBusProperties;

	@Autowired
	private HeartBeatService heartBeatService;

	@Autowired
	private AdminRemoteService remoteService;

	private static final int TIME_OUT = 2 * 60 * 1000;

	@EventListener(classes = ApplicationReadyEvent.class)
	public void init() throws Throwable {
		if (!inited) {
			log.warn("modbus未连接");
			return;
		}
		while (GlobalSetting.getInstance().getBoxNo() == CommonEum.MODBUS_V_0.getIntValue()) {
			readBoxNo();
			TimeUnit.SECONDS.sleep(5);
		}
		heartBeatService.reportHeartBean();


	}


	@Override
	public Pair<Integer, String> readSystemStatus() {
		int status = this.readValue(ModbusOrderEnum.R_SYSTEM_STATUS);
		String zh = remoteService.getValueByKey("ucs_system_status", String.valueOf(status));
		return new Pair<>(status, zh);
	}

	@Override
	public Map<Integer, String> readWarning() {
		int warning = this.readValue(ModbusOrderEnum.R_WARNING);
		//取字典缓存
		Map<String, String> dicByType = remoteService.getValueByGroup("ucs_warning");
		Map<Integer, String> result = new HashMap<>();
		dicByType.forEach((bit, zh) -> {
			int parseInt = Integer.parseInt(bit);
			int warning1 = ((warning >> parseInt) & 0x0001) & 0xffff;
			//bit位置为1 则有告警
			if (warning1 == CommonEum.MODBUS_V_1.getIntValue()) {
				result.put(parseInt, zh);
			}
		});
		return result;
	}

	@Override
	public boolean triggerGoodsOut(int x, int y) {
		boolean row = this.writeValue(ModbusOrderEnum.W_GOODS_ROW, y);
		boolean col = this.writeValue(ModbusOrderEnum.W_GOODS_COLUMN, x);
		if (row && col) {
			return this.writeBit(ModbusOrderEnum.W_CONTROL_PICK_UP, CommonEum.MODBUS_V_1.getIntValue());
		}
		log.error("写入取货的行和列失败");
		throw ErrorEnum.GOODS_OUT_EXCEPTION.exp();
	}

	@Override
	public String getGoodsOutResult(int x, int y) {
		return this.blockProcess(TIME_OUT, () -> {
			//取货超时
			if (CommonEum.MODBUS_V_1.getIntValue() == readBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_TIME_OUT)) {
				this.writeBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_TIME_OUT, CommonEum.MODBUS_V_0.getIntValue());
				log.error("取货超时 第{}排 第{}货道", y, x);
				return MODBUS_PICKUP_STATUS_TIME_OUT.getStringValue();
			}
			//取货失败
			if (CommonEum.MODBUS_V_1.getIntValue() == readBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_FAILED)) {
				this.writeBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_FAILED, CommonEum.MODBUS_V_0.getIntValue());
				//再次校验超时
				if (CommonEum.MODBUS_V_1.getIntValue() == readBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_TIME_OUT)) {
					this.writeBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_TIME_OUT, CommonEum.MODBUS_V_0.getIntValue());
					log.error("取货超时 第{}排 第{}货道", y, x);
					return MODBUS_PICKUP_STATUS_TIME_OUT.getStringValue();
				}
				log.error("取货失败 第{}排 第{}货道", y, x);
				return MODBUS_PICKUP_STATUS_FAILURE.getStringValue();
			}
			//取货完成
			if (CommonEum.MODBUS_V_1.getIntValue() == readBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_COMPLETE)) {
				this.writeBit(ModbusOrderEnum.R_W_CONTROL_PICK_UP_COMPLETE, CommonEum.MODBUS_V_0.getIntValue());
				return MODBUS_PICKUP_STATUS_SUCCESS.getStringValue();
			}
			return null;
		}, ErrorEnum.GOODS_OUT_TIME_OUT);

	}

	@Override
	public int[] scanChannels() {
		//首先开始货道扫描 ，然后监控结果
		this.writeBit(ModbusOrderEnum.W_CONTROL_GOODS_SCAN, CommonEum.MODBUS_V_1.getIntValue());
		return this.blockProcess(TIME_OUT, () -> {
			//扫描失败
			if (CommonEum.MODBUS_V_1.getIntValue() == readBit(R_W_CONTROL_GOODS_SCAN_FAILED)) {
				this.writeBit(R_W_CONTROL_GOODS_SCAN_FAILED, CommonEum.MODBUS_V_0.getIntValue());
				throw ErrorEnum.SCAN_CHANNEL_FAILURE.exp();
			}
			//成功
			if (CommonEum.MODBUS_V_1.getIntValue() == readBit(R_W_CONTROL_GOODS_SCAN_SUCCESS)) {
				this.writeBit(R_W_CONTROL_GOODS_SCAN_SUCCESS, CommonEum.MODBUS_V_0.getIntValue());
				int countRows = readValue(ModbusOrderEnum.R_COUNT_ROWS);
				int[] result = new int[countRows];
				for (int i = 0; i < countRows; i++) {
					int colInt = i + 1;
					int col = readValue(ModbusOrderEnum.valueOf("R_COLUMNS_COUNT_IN_ROW" + colInt));
					result[i] = col;
				}
				return result;


			}
			return null;

		}, ErrorEnum.SCAN_CHANNEL_TIME_OUT);
	}

	@Override
	public LightControlMode readLightControlMode() {

		int lightMode = readValue(ModbusOrderEnum.R_W_LIGHT_CONTROL);
		if (lightMode == CommonEum.MODBUS_LIGHT_MODE_DURATION.getIntValue()) {
			int onTime = readValue(ModbusOrderEnum.R_W_LIGHT_ON_DURATION);
			int offTime = readValue(ModbusOrderEnum.R_W_LIGHT_OFF_DURATION);
			return new LightControlMode(lightMode, onTime, offTime);
		}
		LightControlMode lightControlMode = new LightControlMode();
		lightControlMode.setMode(lightMode);
		return lightControlMode;
	}

	@Override
	public boolean writeLightControlMode(LightControlMode lightControlMode) {
		boolean wrightMode = this.writeValue(ModbusOrderEnum.R_W_LIGHT_CONTROL, lightControlMode.getMode());
		boolean on = true;
		boolean off = true;
		if (lightControlMode.getMode() == CommonEum.MODBUS_LIGHT_MODE_DURATION.getIntValue()) {
			on = this.writeValue(ModbusOrderEnum.R_W_LIGHT_ON_DURATION, lightControlMode.getOnTime());
			off = this.writeValue(ModbusOrderEnum.R_W_LIGHT_OFF_DURATION, lightControlMode.getOffTime());
		}
		return wrightMode && on && off;
	}

	@Override
	public Map<Integer, String> checkBug() {
		int bug = this.readValue(ModbusOrderEnum.R_BUG_CHECK);

		Map<String, String> dicByType = remoteService.getValueByGroup("check_bug_type");
		HashMap<Integer, String> map = new HashMap<>();

		dicByType.forEach((key, zh) -> {
			int parseInt = Integer.parseInt(key);//bit位
			int bug1 = ((bug >> parseInt) & 0x0001) & 0xffff;
			//bit位置为1 则有告警
			if (bug1 == CommonEum.MODBUS_V_1.getIntValue()) {
				map.put(parseInt, zh);
			}

		});
		return map;
	}

	@Override
	public boolean checkDoorOpened() {
		//只检查门是否开启
		int i = readBit(ModbusOrderEnum.R_BUG_CHECK, 0);//第0位是门故障
		return i == CommonEum.MODBUS_V_1.getIntValue();
	}

	@Override
	public boolean confirmPickUp() {
		return writeBit(W_CONTROL_CONFIRM_PICK_UP, CommonEum.MODBUS_V_1.getIntValue());
	}

	@Override
	public boolean writeValue(int position, int value) {
		try {
			int value1 = doWriteValue(position, value);
			if (value1 == value) {
				log.debug("【下位机交互】--->直接写入,地址：{}，值：{}", position, value);
				return true;
			}
		} catch (ModbusTransportException e) {
			log.error("Modbus write value error,position:{},value:{}", position, value, e);

		}
		return false;
	}

	@Override
	public boolean startRefund() {
		return this.writeBit(W_CONTROL_RETURN_GOODS, CommonEum.MODBUS_V_1.getIntValue());
	}

	@Override
	public boolean endRefund() {
		boolean b = this.writeBit(W_CONTROL_RETURN_GOODS_COMPLETE, CommonEum.MODBUS_V_1.getIntValue());
		boolean b1 = b && refundStatus();
		//清除
		new Thread(() -> {
			writeBit(W_CONTROL_RETURN_GOODS, CommonEum.MODBUS_V_0.getIntValue());
			writeBit(W_CONTROL_RETURN_GOODS_COMPLETE, CommonEum.MODBUS_V_0.getIntValue());
			writeBit(W_CONTROL_RETURN_GOODS_SUCCESS, CommonEum.MODBUS_V_0.getIntValue());
			writeBit(W_CONTROL_RETURN_GOODS_FAIL, CommonEum.MODBUS_V_0.getIntValue());

		}).start();
		return  b1;
	}

	@Override
	public boolean refundStatus() {
		return blockProcess(REFUND_TIME_OUT, () -> {
			int bit = readBit(W_CONTROL_RETURN_GOODS_SUCCESS);
			boolean success = bit == CommonEum.MODBUS_V_1.getIntValue();
			if (success){
				return true;
			}
			int bit1 = readBit(W_CONTROL_RETURN_GOODS_FAIL);
			return bit1 == CommonEum.MODBUS_V_1.getIntValue()?false:null;

		},ErrorEnum.REFUND_TIME_OUT);
	}

	private void readBoxNo() throws RuntimeException {
		// 取boxNo并set
		this.slaveId = modBusProperties.getSlaveId();
		int boxNo = this.readValue(ModbusOrderEnum.R_BOX_NO);
		if (boxNo != CommonEum.MODBUS_NO_READ.getIntValue() && boxNo != 0) {
			GlobalSetting.getInstance().setBoxNo(boxNo);
			log.info("盒子no已读取到：{}", GlobalSetting.getInstance().getBoxNo());

		} else {
			log.warn("modbus已连接,但盒子设备号读取失败,读取的地址为：{}", ModbusOrderEnum.R_BOX_NO);
		}
	}

	private int readBit(ModbusOrderEnum orderEnum) {
		return readBit(orderEnum, orderEnum.getBit());
	}

	private int readBit(ModbusOrderEnum orderEnum, int bit) {
		int result = this.readValue(orderEnum);
		if (result == CommonEum.MODBUS_NO_READ.getIntValue()) {
			return CommonEum.MODBUS_V_0.getIntValue();
		}
		return ((result >> bit) & 0x0001) & 0xffff;
	}

	private boolean writeBit(ModbusOrderEnum orderEnum, int value) {
		if (value != 1 && (value != 0)) {
			throw new RuntimeException("bit位只能0/1");
		}
		//对于bit位的操作，要保证不改变对应寄存器的其他bit位
		int readValue = this.readValue(orderEnum);

		int v;
		int bit = orderEnum.getBit();

		if (readValue > CommonEum.MODBUS_NO_READ.getIntValue()) {
			v = value == 1 ? (readValue | (1 << (bit))) : readValue & (~(1 << (bit)));//置为0
		} else {
			v = value == 1 ? (1 << (bit)) : (~(1 << (bit)));
		}
		return this.writeValue(orderEnum, v);
	}

	private int readValue(ModbusOrderEnum orderEnum) {
		checkStatus();
		return rawReadValue(orderEnum);
	}

	private boolean writeValue(ModbusOrderEnum modbusOrderEnum, int value) {
		checkStatus();
		return rawWriteValue(modbusOrderEnum, value);
	}

	/**
	 * 不校验状态进行写，需要手动校验modbus状态
	 *
	 * @param modbusOrderEnum
	 * @param value
	 *
	 * @return
	 */
	private boolean rawWriteValue(ModbusOrderEnum modbusOrderEnum, int value) {
		checkValue(value);
		try {
			int result = this.doWriteValue(modbusOrderEnum.getValue(), value);
			if (result == value) {
					log.debug("【下位机交互】 uc write--> ORDER:{},value:{},binary:{}", modbusOrderEnum, result,
							Integer.toBinaryString(result));


				return true;
			} else {
				log.error("【下位机交互】uc 写入值与返回值不相等 ORDER:{},result:{}", modbusOrderEnum, result);
			}
		} catch (ModbusTransportException e) {
			log.error("【下位机交互】 写入下位机数据异常，ORDER:{},value:{}", modbusOrderEnum, value, e);
		}
		return false;
	}

	private int doWriteValue(int pos, int value) throws ModbusTransportException {
		WriteRegisterRequest writeRequest = new WriteRegisterRequest(this.slaveId, pos, value);
		WriteRegisterResponse response = (WriteRegisterResponse) modbusMaster.send(writeRequest);
		return response.getWriteValue();
	}


	private void checkValue(int value) {

		if (value < 0) {
			String msg = String.format("值%s小于0，无法写入下位机", value);
			log.warn(msg);
			throw new RuntimeException(msg);
		}
	}

	private void checkStatus() {
		if (!inited) {
			log.warn("modbus未连接");
			throw new RuntimeException("modbus未连接,无法读写！！！");
		}
	}

	/**
	 * 不校验状态，使用前需要手动校验
	 *
	 * @param orderEnum
	 *
	 * @return
	 */
	private int rawReadValue(ModbusOrderEnum orderEnum) {
		try {
			ReadHoldingRegistersRequest readRequest = new ReadHoldingRegistersRequest(this.slaveId,
					orderEnum.getValue(), CommonEum.MODBUS_READ_NUMBER.getIntValue());
			//同步发送
			ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) modbusMaster.send(readRequest);
			// 所读的位置不存在返回null
			if (response.getData() != null) {
				int result = response.getShortData()[0];
					log.debug("【下位机交互】 ucs read  <---- ORDER:{},result:{}，binary:{}", orderEnum, result,
							Integer.toBinaryString(result));


				return result;
			}
		} catch (Exception e) {
			log.error("【下位机交互】 读取下位机数据异常，ORDER :{},", orderEnum, e);
		}
		return (short) CommonEum.MODBUS_NO_READ.getIntValue();
	}

	private <T> T blockProcess(long timeout, Supplier<T> supplier, ErrorEnum timeoutError) {
		long start = System.currentTimeMillis();
		while (System.currentTimeMillis() - start < timeout) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
			}

			T result = supplier.get();
			if (result != null) {
				return result;
			}

		}
		throw timeoutError.exp();
	}
}
